#![allow(non_snake_case)]
use crate::ValidType;
use leptos::{ev, html, prelude::*};
use shq_common::prelude::Size;
use std::fmt::Display;
use std::ops::{Add, Sub};
use std::str::FromStr;
use thaw_utils::{ArcOneCallback, BoxOneCallback, ComponentRef, Model};

use super::{Input, InputPrefix, InputRef, InputSuffix};
use icondata as i;
use leptos_icons::*;

#[component]
pub fn InputNumber<T>(
    #[prop(optional, into)] value: Model<Option<T>>,
    #[prop(optional, into)] placeholder: MaybeProp<String>,
    #[prop(into)] step: MaybeProp<T>,
    #[prop(optional, into)] disabled: MaybeProp<bool>,
    #[prop(optional, into)] valid_type: MaybeProp<ValidType>,
    #[prop(optional, into)] size: MaybeProp<Size>,
    #[prop(optional, into)] on_change: Option<ArcOneCallback<Option<T>>>,
    #[prop(optional, into)] on_blur: Option<BoxOneCallback<Option<T>>>,
    #[prop(default=None)] range: Option<(T, T)>,
    #[prop(optional)] input_prefix: Option<InputPrefix>,
    #[prop(optional)] input_suffix: Option<InputSuffix>,
    #[prop(optional)] comp_ref: ComponentRef<InputRef>,
    #[prop(default=true.into(), into)] border: MaybeProp<bool>,
    #[prop(optional)] inner_max_width: u32,
    #[prop(optional)] is_init_focus: bool,
) -> impl IntoView
where
    T: Add<Output = T> + Sub<Output = T>,
    T: Default
        + Display
        + Copy
        + Clone
        + FromStr
        + PartialEq
        + PartialOrd
        + ToString
        + Send
        + Sync
        + 'static,
{
    let input_value: RwSignal<Option<String>> = RwSignal::new(None);
    Effect::new(move |prev| {
        let val = value.get();
        if let Some(prev) = prev {
            if val == prev {
                return prev;
            }
        }
        input_value.set(val.as_ref().map(|v| v.to_string()));
        val
    });
    let on_change_clone = on_change.clone();
    let allow_value = ArcOneCallback::<String, bool>::new(move |v: String| {
        if v.is_empty() {
            value.set(None);
            return true;
        }
        let Ok(nv) = v.parse::<T>() else { return false };
        if let Some((_, max)) = &range {
            if nv > *max {
                return false;
            }
        }
        if v.contains('.') && nv.to_string().contains('.') == false {
            return true;
        }
        value.set(Some(nv));
        if let Some(on_change) = on_change_clone.as_ref() {
            on_change(Some(nv));
        }
        true
    });
    let on_internal_blur = move |ev: ev::FocusEvent| {
        if let Some(on_blur) = on_blur.as_ref() {
            let input_value = event_target_value(&ev);
            let val = if input_value.is_empty() {
                None
            } else {
                Some(input_value.parse::<T>().unwrap_or_default())
            };
            on_blur(val);
        }
    };
    let input_ref = NodeRef::<html::Input>::new();
    input_ref.on_load(move |_| {
        comp_ref.load(InputRef { input_ref });
    });
    let step = step.get().unwrap_or_else(|| T::default());
    let on_change_clone2 = on_change.clone();
    let on_change_clone3 = on_change.clone();
    view! {
        <Input
            value=input_value
            allow_value
            on_blur=on_internal_blur
            placeholder
            size
            disabled
            valid_type
            comp_ref=comp_ref
            input_prefix
            border
            inner_max_width
            is_init_focus
            on:keydown=move |e: ev::KeyboardEvent| {
                let key=e.key();
                let nv=if key == *"ArrowUp" || key == *"+"{
                    e.prevent_default();
                    let v=value.get_untracked().unwrap_or_default() + step;
                    Some(v)
                }else if key == *"ArrowDown" || key == *"-" {
                    e.prevent_default();
                    let v=value.get_untracked().unwrap_or_default() - step;
                    Some(v)
                }else{
                    None
                };
                if let Some(nv)=nv {
                    value.set(Some(nv));
                    if let Some(on_change) = on_change.as_ref() {
                        on_change(Some(nv));
                    }
                }
            }
        >
            <InputSuffix slot>
                <div class="join join">
                    <button class="btn btn-square btn-xs join-item" on:click=move |e: ev::MouseEvent| {
                        e.prevent_default();
                        let nv=value.get_untracked().unwrap_or_default() + step;
                        value.set(Some(nv));
                        if let Some(on_change) = on_change_clone2.as_ref() {
                            on_change(Some(nv));
                        }
                    }>
                        <Icon icon=i::AiPlusOutlined/>
                    </button>
                    <button class="btn btn-square btn-xs join-item" on:click=move |e: ev::MouseEvent| {
                        e.prevent_default();
                        let nv=value.get_untracked().unwrap_or_default() - step;
                        value.set(Some(nv));
                        if let Some(on_change) = on_change_clone3.as_ref() {
                            on_change(Some(nv));
                        }
                    }>
                        <Icon icon=i::AiMinusOutlined/>
                    </button>
                    {if let Some(suffix) = input_suffix.and_then(|suffix| suffix.if_.then_some(suffix)) {
                        view! { {(suffix.children)()} }.into()
                    } else {
                        None
                    }}
                </div>
            </InputSuffix>
        </Input>
    }
}
